package nachos.userprog;

import nachos.machine.*;
import nachos.threads.*;
import nachos.userprog.*;

/**
 * 为机器控制台提供一个简单、同步的接口
 * 还可以通过 OpenFile 对象访问该接口
 */
public class SynchConsole {
    /**
     * 分配一个新的 SynchConsole
     *
     * @param console 要使用的底层串行控制台
     */
    public SynchConsole(SerialConsole console) {
        this.console = console;

        Runnable receiveHandler = new Runnable() {
            public void run() {
                receiveInterrupt();
            }
        };
        Runnable sendHandler = new Runnable() {
            public void run() {
                sendInterrupt();
            }
        };
        console.setInterruptHandlers(receiveHandler, sendHandler);
    }

    /**
     * 返回接收到的下一个无符号字节（范围为 0 到 255）
     *
     * 如果一个字节尚未到达，则阻塞直到一个字节到达，或立即返回，具体取决于 block 的值
     *
     * @param block true 如果 readByte() 没有可用的字节，则应等待一个字节
     * @return 读取的下一个字节，如果 block 为 false 且没有可用字节，则为 -1
     */
    public int readByte(boolean block) {
        int value;
        boolean intStatus = Machine.interrupt().disable();
        readLock.acquire();

        if (block || charAvailable) {
            charAvailable = false;
            readWait.P();

            value = console.readByte();
            Lib.assertTrue(value != -1);
        } else {
            value = -1;
        }

        readLock.release();
        Machine.interrupt().restore(intStatus);
        return value;
    }

    /**
     * 返回一个 OpenFile，可用于将其作为文件读取
     *
     * @return 可以读取此控制台的文件
     */
    public OpenFile openForReading() {
        return new File(true, false);
    }

    private void receiveInterrupt() {
        charAvailable = true;
        readWait.V();
    }

    /**
     * 发送一个字节
     * 阻塞直到发送完成
     *
     * @param value 要发送的字节（高24位被忽略）
     */
    public void writeByte(int value) {
        writeLock.acquire();
        console.writeByte(value);
        writeWait.P();
        writeLock.release();
    }

    /**
     * 返回一个 OpenFile，可将其作为文件写入
     *
     * @return 可以写入此控制台的文件
     */
    public OpenFile openForWriting() {
        return new File(false, true);
    }

    private void sendInterrupt() {
        writeWait.V();
    }

    private boolean charAvailable = false;

    private SerialConsole console;
    private Lock readLock = new Lock();
    private Lock writeLock = new Lock();
    private Semaphore readWait = new Semaphore(0);
    private Semaphore writeWait = new Semaphore(0);

    private class File extends OpenFile {
        File(boolean canRead, boolean canWrite) {
            super(null, "SynchConsole");

            this.canRead = canRead;
            this.canWrite = canWrite;
        }

        public void close() {
            canRead = canWrite = false;
        }

        public int read(byte[] buf, int offset, int length) {
            if (!canRead)
                return 0;

            int i;
            for (i = 0; i < length; i++) {
                int value = SynchConsole.this.readByte(false);
                if (value == -1)
                    break;

                buf[offset + i] = (byte) value;
            }

            return i;
        }

        public int write(byte[] buf, int offset, int length) {
            if (!canWrite)
                return 0;

            for (int i = 0; i < length; i++)
                SynchConsole.this.writeByte(buf[offset + i]);

            return length;
        }

        private boolean canRead, canWrite;
    }
}
